﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.WebSockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using Cysharp.Threading.Tasks;
namespace RTT
{
    /// <summary>
    /// 返回值结果格式为json
    /// </summary>
    [Serializable]
    public struct JsonData
    {
        /// <summary>
        /// 结果标识，started:握手，result:结果，error:异常
        /// </summary>
        public string action;

        /// <summary>
        /// 结果码(具体见错误码)
        /// </summary>
        public string code;

        /// <summary>
        /// 转写结果数据
        /// </summary>
        public string data;

        /// <summary>
        /// 描述
        /// </summary>
        public string desc;

        /// <summary>
        /// 会话ID
        /// 主要用于DEBUG追查问题，如果出现问题，可以提供sid帮助确认问题。
        /// </summary>
        public string sid;
    }

    [Serializable]
    public struct Data
    {
        /// <summary>
        /// 转写结果序号	从0开始
        /// </summary>
        public string seg_id;

        [Serializable]
        public struct CN
        {
            [Serializable]
            public struct ST
            {
                [Serializable]
                public struct RT
                {
                    [Serializable]
                    public class WS
                    {
                        [Serializable]
                        public class CW
                        {
                            /// <summary>
                            /// 词识别结果
                            /// </summary>
                            public string w;

                            /// <summary>
                            /// 词标识  n-普通词；s-顺滑词（语气词）；p-标点
                            /// </summary>
                            public string wp;
                        }

                        [SerializeField] public CW[] cw;

                        /// <summary>
                        /// 词在本句中的开始时间，单位是帧，1帧=10ms  即词在整段语音中的开始时间为(bg+wb*10)ms
                        /// 中间结果的 wb 为 0
                        /// </summary>
                        public string wb;

                        /// <summary>
                        /// 词在本句中的结束时间，单位是帧，1帧=10ms  即词在整段语音中的结束时间为(bg+we*10)ms
                        /// 中间结果的 we 为 0
                        /// </summary>
                        public string we;
                    }

                    [SerializeField] public WS[] ws;
                }

                [SerializeField] public RT[] rt;

                /// <summary>
                /// 句子在整段语音中的开始时间，单位毫秒(ms)
                /// 中间结果的bg为准确值
                /// </summary>
                public string bg;

                /// <summary>
                /// 结果类型标识	0-最终结果；1-中间结果
                /// </summary>
                public string type;

                /// <summary>
                /// 句子在整段语音中的结束时间，单位毫秒(ms)
                /// 中间结果的ed为0
                /// </summary>
                public string ed;
            }

            [SerializeField] public ST st;
        }

        [SerializeField] public CN cn;

        /// <summary>
        /// 
        /// </summary>
        public string ls;

        public override string ToString()
        {
            return string.Format("seg_id:{0}, cn:{1}, ls:{2}", seg_id.ToString(), cn.ToString(), ls.ToString());
        }
    }

    public class XunfeiRTT
    {
        //讯飞控制台上所创建的应用对应的appid、appkey
        private const string appid = "ad77adcf";
        private const string appkey = "7ec02c1cd93da0583ac5d6a84a131e76";

        private string timeStamp;
        private string baseString;
        private string toMd5;
        private float[] samples;
        private int gotsamples;
        private string signa;

        private AudioClip recordcClip;
        private ClientWebSocket ws;
        private CancellationToken ct;


        //最大录音时长
        private int MAX_RECORD_LENGTH = 20;

        /// <summary>
        /// 语音识别回调事件
        /// </summary>
        public event Action<string> onRTT;

        public XunfeiRTT()
        {

        }


        /// <summary>
        /// 开始实施对话
        /// </summary>
        public void OpenRTT()
        {
            if (ws != null && ws.State == WebSocketState.Open)
            {
                Debug.LogWarning("开始语音识别失败！，等待上次识别连接结束");
                return;
            }

            if (Microphone.devices.Length == 0)
            {
                Debug.LogError("未检测到可用的麦克风");
                return;
            }

            ConnectASR_Aysnc();

            recordcClip = Microphone.Start(null, true, MAX_RECORD_LENGTH, 16000);
        }
        /// <summary>
        /// 关闭实时对话
        /// </summary>
        public async void StopRTT()
        {
            try
            {
                if (ws != null)
                {
                    //关掉发送音频的协程
                    App.Instance.StopCoroutine(SendAudioClip());

                    //Debug.Log("发送结束标识" + ws.State);
                    //音频数据上传完成后,客户端需发送一个 {"end": true} 到服务端作为结束标识
                    await ws.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("{\"end\": true}")),
                        WebSocketMessageType.Binary,
                        true, new CancellationToken());
                    ws.Dispose();
                    //await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "关闭WebSocket连接", new CancellationToken());
                    Microphone.End(null);

                    App.Instance.StartCoroutine(StopRecord());
                }
            }
            catch
            {

            }

        }

        private IEnumerator StopRecord()
        {
            yield return new WaitUntil(() => ws.State != WebSocketState.Open);
            Debug.Log("识别结束，停止录音");
        }

        private async void ConnectASR_Aysnc()
        {
            ws = new ClientWebSocket();
            ct = new CancellationToken();
            Uri url = GetUri();
            await ws.ConnectAsync(url, ct);
            App.Instance.StartCoroutine(SendAudioClip());
            while (ws.State == WebSocketState.Open)
            {
                try
                {
                    var result = new byte[4096];
                    await ws.ReceiveAsync(new ArraySegment<byte>(result), ct); //接受数据
                    List<byte> list = new List<byte>(result);
                    while (list[list.Count - 1] == 0x00) list.RemoveAt(list.Count - 1); //去除空字节
                    string str = Encoding.UTF8.GetString(list.ToArray());

                    if (string.IsNullOrEmpty(str))
                    {
                        return;
                    }

                    try
                    {
                        JsonData jsonData = JsonUtility.FromJson<JsonData>(str);
                        if (jsonData.action.Equals("started"))
                        {
                            // Debug.Log("握手成功！");
                        }
                        else if (jsonData.action.Equals("result"))
                        {
                            //Debug.Log("返回结果：" + str);
                            AnalysisResult(jsonData.data);
                        }
                        else if (jsonData.action.Equals("error"))
                        {
                            Debug.Log("Error: " + jsonData.desc);
                        }
                    }
                    catch (Exception ex)
                    {
                        Debug.LogError(ex.Message + str);
                    }

                }
                catch
                {

                }

                //}
                //catch (Exception ex)
                //{


                //    Debug.LogError(ex.InnerException);
                //    Debug.LogError(ex.Message);

                //}
            }

            Debug.LogWarning("断开连接");
        }


        /// <summary>
        /// 发送音频片段
        /// </summary>
        /// <param name="socket"></param>
        /// <returns></returns>
        private IEnumerator SendAudioClip()
        {
            yield return new WaitWhile(() => Microphone.GetPosition(null) <= 0);
            float t = 0;
            int position = Microphone.GetPosition(null);
            const float waitTime = 0.04f; //每隔40ms发送音频
            WaitForSecondsRealtime waitsr = new WaitForSecondsRealtime(waitTime);
            CancellationToken token = new CancellationToken();
            while (position < recordcClip.samples && ws.State == WebSocketState.Open)
            {
                t += waitTime;

                yield return waitsr;

                position = Microphone.GetPosition(null);
                if (gotsamples == 16000 * MAX_RECORD_LENGTH)  //获取到数据的最后一位了
                    gotsamples = 0;

                samples = null;
                if (gotsamples < position)//正常境况下循环
                {
                    samples = new float[(position - gotsamples) * recordcClip.channels];
                    recordcClip.GetData(samples, gotsamples);
                    gotsamples = position;
                }
                else if (position < gotsamples)//循环过圈
                {
                    samples = new float[(MAX_RECORD_LENGTH * 16000 - gotsamples) * recordcClip.channels];
                    recordcClip.GetData(samples, gotsamples);
                    gotsamples = MAX_RECORD_LENGTH * 16000;

                }

                if (samples != null)
                {
                    var _data = GetAudioByteArray(samples);
                    try
                    {
                         ws.SendAsync(new ArraySegment<byte>(_data), WebSocketMessageType.Binary, true, token); //发送数据
                    }
                    catch
                    {

                       
                    }
                   
                }
            }
        }


        public byte[] GetAudioByteArray(float[] clipdata)
        {

            int rescaleFactor = 32767;
            byte[] outData = new byte[clipdata.Length * 2];
            for (int i = 0; i < clipdata.Length; i++)
            {
                short temshort = (short)(clipdata[i] * rescaleFactor);
                byte[] temdata = BitConverter.GetBytes(temshort);
                outData[i * 2] = temdata[0];
                outData[i * 2 + 1] = temdata[1];
            }
            return outData;
        }
        /// <summary>
        /// 获取识别并返回字符串
        /// </summary>
        /// <param name="data">所获取的识别的Json字符串</param>
        /// <returns>所识别的连贯的一句话</returns>
        private void AnalysisResult(string data)
        {
            Data result = JsonUtility.FromJson<Data>(data); // 等效于 JsonConvert.DeserializeObject<Data>(data)
            StringBuilder stringBuilder = new StringBuilder();

            //Debug.Log(result.cn.st.rt[0].ws.Length);
            for (int i = 0; i < result.cn.st.rt[0].ws.Length; i++)
            {
                //******只有w字段有用，将其提取出来即可******
                stringBuilder.Append(result.cn.st.rt[0].ws[i].cw[0].w);
            }

            string _thisType = result.cn.st.type;
            string content = stringBuilder.ToString();


            //Debug.Log(content + "***" + _thisType);
            //type	结果类型标识	0-最终结(即这句话说完了)；1-中间结果(即这句话没说完，下一句转写结果可能推翻前面的内容)
            if (_thisType.Equals("0"))
            {
                //Debug.Log(content);
                StopRTT();
                onRTT?.Invoke(content);
            }
            else
            {

            }
        }

        /// <summary>
        /// 获取音频流片段
        /// </summary>
        /// <param name="start">起始采样点</param>
        /// <param name="length">采样长度</param>
        /// <param name="recordedClip">音频</param>
        /// <returns></returns>
        public byte[] GetAudioClip(int start, int length, AudioClip recordedClip)
        {
            float[] soundata = new float[length];
            recordedClip.GetData(soundata, start);
            int rescaleFactor = 32767;
            byte[] outData = new byte[soundata.Length * 2];
            for (int i = 0; i < soundata.Length; i++)
            {
                short temshort = (short)(soundata[i] * rescaleFactor);
                byte[] temdata = BitConverter.GetBytes(temshort);
                outData[i * 2] = temdata[0];
                outData[i * 2 + 1] = temdata[1];
            }

            return outData;
        }

        /// <summary>
        /// 获得请求URI
        /// </summary>
        /// <returns>请求的URI</returns>
        private Uri GetUri()
        {
            //精确到秒
            timeStamp = GetTimeStamp();

            //baseString由appid和当前时间戳ts拼接而成
            baseString = appid + timeStamp;

            //对baseString进行MD5
            toMd5 = ToMD5(baseString);

            //以apiKey为key对MD5之后的baseString进行HmacSHA1加密
            //然后再对加密后的字符串进行base64编码
            signa = ToHmacSHA1(toMd5, appkey);

            string requestUrl = string.Format("wss://rtasr.xfyun.cn/v1/ws?appid={0}&ts={1}&signa={2}&pd=tech", appid,
                timeStamp, UrlEncode(signa));
            Debug.Log("requestUrl: " + requestUrl);
            return new Uri(requestUrl);
        }

        #region 一些加密算法

        /// <summary>
        /// 对字符串进行UrlEncode转码
        /// </summary>
        /// <param name="str">需要转码的字符串</param>
        /// <returns>经过UrlEncode转码的字符串</returns>
        public string UrlEncode(string str)
        {
            StringBuilder sb = new StringBuilder();
            byte[] byStr = System.Text.Encoding.UTF8.GetBytes(str); //默认是System.Text.Encoding.Default.GetBytes(str)
            for (int i = 0; i < byStr.Length; i++)
            {
                sb.Append(@"%" + Convert.ToString(byStr[i], 16));
            }

            return (sb.ToString());
        }

        /// <summary>
        /// 获取时间戳
        /// </summary>
        /// <returns>时间戳，精确到秒</returns>
        public string GetTimeStamp()
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }

        /// <summary>
        /// MD5字符串加密
        /// </summary>
        /// <param name="txt">需要加密的字符串</param>
        /// <returns>加密后字符串</returns>
        public string ToMD5(string txt)
        {
            using (MD5 mi = MD5.Create())
            {
                byte[] buffer = Encoding.Default.GetBytes(txt);
                //开始加密
                byte[] newBuffer = mi.ComputeHash(buffer);
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < newBuffer.Length; i++)
                {
                    sb.Append(newBuffer[i].ToString("x2"));
                }

                return sb.ToString();
            }
        }

        /// <summary>
        /// HMACSHA1算法加密并返回ToBase64String
        /// </summary>
        /// <param name="text">要加密的原串</param>
        ///<param name="key">私钥</param>
        /// <returns>返回一个签名值(即哈希值)</returns>
        public string ToHmacSHA1(string text, string key)
        {
            //HMACSHA1加密
            HMACSHA1 hmacsha1 = new HMACSHA1();
            hmacsha1.Key = System.Text.Encoding.UTF8.GetBytes(key);

            byte[] dataBuffer = System.Text.Encoding.UTF8.GetBytes(text);
            byte[] hashBytes = hmacsha1.ComputeHash(dataBuffer);

            return Convert.ToBase64String(hashBytes);
        }

        #endregion
    }
}


